package org.jeecg.modules.inOrder.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.constant.WarehouseConstant;
import org.jeecg.modules.basicCompartment.entity.BasicCompartment;
import org.jeecg.modules.basicCompartment.service.IBasicCompartmentService;
import org.jeecg.modules.basicShelf.entity.BasicShelf;
import org.jeecg.modules.basicShelf.service.IBasicShelfService;
import org.jeecg.modules.basicShelfType.entity.BasicShelfType;
import org.jeecg.modules.basicShelfType.service.IBasicShelfTypeService;
import org.jeecg.modules.basicSkuInfo.entity.BasicSkuInfo;
import org.jeecg.modules.basicSkuInfo.service.IBasicSkuInfoService;
import org.jeecg.modules.basicStationInfo.entity.BasicStationInfo;
import org.jeecg.modules.basicStationInfo.service.IBasicStationInfoService;
import org.jeecg.modules.inOrder.entity.CallShelvesVO;
import org.jeecg.modules.inOrder.entity.InOrder;
import org.jeecg.modules.inOrder.entity.InOrderDetail;
import org.jeecg.modules.inOrder.entity.ShelfPTO;
import org.jeecg.modules.inOrder.service.IInOrderDetailService;
import org.jeecg.modules.inOrder.service.IInOrderService;
import org.jeecg.modules.inOrder.vo.EnterVo;
import org.jeecg.modules.inOrder.vo.InOrderPage;
import org.jeecg.modules.inOrderTaskMain.entity.InOrderTaskDetail;
import org.jeecg.modules.inOrderTaskMain.entity.InOrderTaskMain;
import org.jeecg.modules.inOrderTaskMain.service.IInOrderTaskDetailService;
import org.jeecg.modules.inOrderTaskMain.service.IInOrderTaskMainService;
import org.jeecg.modules.inventory.entity.InventoryInfo;
import org.jeecg.modules.inventory.service.IInventoryInfoService;
import org.jeecg.modules.reportLabel.entity.ReportLabel;
import org.jeecg.modules.reportLabel.service.IReportLabelService;
import org.jeecg.modules.shelfTask.entity.ShelfTask;
import org.jeecg.modules.shelfTask.service.IShelfTaskService;
import org.jeecg.modules.system.entity.SysRole;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.system.util.QRCodeUtil;
import org.jeecg.modules.system.util.RMSUtil;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.imageio.ImageIO;
import javax.print.PrintException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Api(tags = "入库管理")
@RestController
@RequestMapping("/inOrder/inOrder")
@Slf4j
public class InOrderController extends JeecgController<InOrder, IInOrderService> {
    @Autowired
    private RMSUtil rmsUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private IInOrderService inOrderService;
    @Autowired
    private IInOrderDetailService inOrderDetailService;
    @Autowired
    private IShelfTaskService shelfTaskService;
    @Autowired
    private IBasicStationInfoService stationService;
    @Autowired
    private IBasicSkuInfoService basicSkuInfoService;
    @Autowired
    private IBasicShelfService shelfService;
    @Autowired
    private IBasicShelfTypeService shelfTypeService;
    @Autowired
    private IBasicCompartmentService basicCompartmentService;
    @Autowired
    private IInOrderTaskMainService inOrderTaskMainService;
    @Autowired
    private IInOrderTaskDetailService inOrderTaskDetailService;
    @Autowired
    private IReportLabelService reportLabelService;
    @Autowired
    private IInventoryInfoService inventoryInfoService;


    /**
     * 分页列表查询
     *
     * @param inOrder
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    @AutoLog(value = "入库管理-分页列表查询")
    @ApiOperation(value = "入库管理-分页列表查询", notes = "入库管理-分页列表查询")
    @GetMapping(value = "/list")
    public Result<?> queryPageList(InOrder inOrder, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
        // 获取当前登录人角色
        SysRole sysRole = sysUserService.selectLoginUserRole();

        QueryWrapper<InOrder> queryWrapper = QueryGenerator.initQueryWrapper(inOrder, req.getParameterMap());
        // 按照当前登录人角色筛选列表数据
        if (WarehouseConstant.RoleCodeEnum.TALLY.getCode().equals(sysRole.getRoleCode())) {
            queryWrapper.in("execute_staff", Arrays.asList(WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY.getCode(), WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY_DRIVER.getCode()));
        } else if (WarehouseConstant.RoleCodeEnum.DRIVER.getCode().equals(sysRole.getRoleCode())) {
            queryWrapper.in("execute_staff", Arrays.asList(WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode(), WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY_DRIVER.getCode()));
        }
        queryWrapper.ne("status", WarehouseConstant.EnterWarehouseEnum.DELETE.getCode());
        queryWrapper.orderByAsc("FIELD(status, '03', '02', '01', '05', '04')");
        queryWrapper.orderByDesc("create_time");
        Page<InOrder> page = new Page<InOrder>(pageNo, pageSize);
        IPage<InOrder> pageList = inOrderService.page(page, queryWrapper);

        List<InOrder> records = pageList.getRecords();
        for (InOrder order : records) {
            if (Objects.equals(WarehouseConstant.RoleCodeEnum.TALLY.getCode(), sysRole.getRoleCode())) {
                InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                        .eq(InOrderTaskMain::getInOrderId, order.getId())
                        .eq(InOrderTaskMain::getExecuteStaff, WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY.getCode()));
                order.setTaskStatus(null == inOrderTaskMain ? order.getStatus() : inOrderTaskMain.getStatus());
            } else if (Objects.equals(WarehouseConstant.RoleCodeEnum.DRIVER.getCode(), sysRole.getRoleCode())) {
                InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                        .eq(InOrderTaskMain::getInOrderId, order.getId())
                        .eq(InOrderTaskMain::getExecuteStaff, WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode()));
                order.setTaskStatus(null == inOrderTaskMain ? order.getStatus() : inOrderTaskMain.getStatus());
            } else {
                order.setTaskStatus(order.getStatus());
            }
        }

        return Result.OK(pageList);
    }

    /**
     * 添加
     *
     * @param inOrderPage 入库单对象
     * @return
     */
    @AutoLog(value = "入库管理-添加入库单")
    @ApiOperation(value = "入库管理-添加入库单", notes = "入库管理-添加入库单")
    @PostMapping(value = "/add")
    public Result<?> add(@RequestBody InOrderPage inOrderPage) {
        // 字段校验
        this.checkColumns(inOrderPage);

        InOrder inOrder = new InOrder();
        BeanUtils.copyProperties(inOrderPage, inOrder);
        inOrderService.saveWarehousingEntry(inOrder, inOrderPage.getInOrderDetailList());
        return Result.OK();
    }

    @AutoLog(value = "入库管理-根据市贩品sku添加入库单")
    @ApiOperation(value = "入库管理-根据市贩品sku添加入库单", notes = "入库管理-根据市贩品sku添加入库单")
    @PostMapping(value = "/addBySku")
    public Result<?> addBySku(@RequestParam(name = "skuCode") String skuCode) {
        String baseUrl = "http://47.104.25.193:55551/jeecg-boot/Inventory/inventory/m/getBySku?skuCode=";
        String url = baseUrl + skuCode;
        JSONObject response = RestUtil.get(url);
        Integer code = response.getInteger("code");
        if (code == 200) {
            JSONArray result = response.getJSONArray("result");
            String now = DateUtils.getNow();
            for (int i = 0; i < result.size(); i++) {
                JSONObject jsonObject = result.getJSONObject(i);
                String containerNum = jsonObject.getString("containerNum");//集装箱号
                Integer number = jsonObject.getInteger("number");//个数
                String sku = jsonObject.getString("skuCode");//品番编码
                Date inTime = jsonObject.getDate("inTime");//入库时间
                InOrder inOrder = new InOrder();
                inOrder.setInOrderCode(now + "_" + (i + 1));
                inOrder.setContainerNo(StringUtils.isBlank(containerNum) ? "-" : containerNum);
                inOrder.setInOrderTime(inTime == null ? new Date() : inTime);
                inOrder.setArea("A");
                inOrder.setGoodsType(2);
                InOrderDetail inOrderDetail = new InOrderDetail();
                inOrderDetail.setSkuCode(sku);
                inOrderDetail.setBoxNumber(number);
                inOrderService.saveMain(inOrder, Collections.singletonList(inOrderDetail));
            }
            return Result.OK("生成成功", null);
        } else {
            return Result.error(response.getString("message"));
        }
    }

    @AutoLog(value = "入库管理-根据PT品sku添加入库单")
    @ApiOperation(value = "入库管理-根据PT品sku添加入库单", notes = "入库管理-根据PT品sku添加入库单")
    @PostMapping(value = "/addBySkuPt")
    public Result<?> addBySkuPt(@RequestParam(name = "skuCode") String skuCode) {
        String baseUrl = "http://47.104.25.193:55551/jeecg-boot/inventoryPt/inventoryPt/m/getBySku?skuCode=";
        String url = baseUrl + skuCode;
        JSONObject response = RestUtil.get(url);
        Integer code = response.getInteger("code");
        if (code == 200) {
            JSONArray result = response.getJSONArray("result");
            String now = DateUtils.getNow();
            for (int i = 0; i < result.size(); i++) {
                JSONObject jsonObject = result.getJSONObject(i);
                String containerNum = jsonObject.getString("containerNum");//集装箱号
                Integer number = jsonObject.getInteger("number");//个数
                String sku = jsonObject.getString("skuCode");//品番编码
                Date inTime = jsonObject.getDate("inTime");//入库时间
                InOrder inOrder = new InOrder();
                inOrder.setInOrderCode(now + "_" + (i + 1));
                inOrder.setContainerNo(StringUtils.isBlank(containerNum) ? "-" : containerNum);
                inOrder.setInOrderTime(inTime == null ? new Date() : inTime);
                inOrder.setArea("A");
                inOrder.setGoodsType(1);
                InOrderDetail inOrderDetail = new InOrderDetail();
                inOrderDetail.setSkuCode(sku);
                inOrderDetail.setBoxNumber(number);
                inOrderService.saveMain(inOrder, Collections.singletonList(inOrderDetail));
            }
            return Result.OK("生成成功", null);
        } else {
            return Result.error(response.getString("message"));
        }
    }

    /**
     * 编辑
     *
     * @param inOrderPage 入库单对象
     * @return
     */
    @AutoLog(value = "入库管理-编辑入库单")
    @ApiOperation(value = "入库管理-编辑入库单", notes = "入库管理-编辑入库单")
    @PutMapping(value = "/edit")
    public Result<?> edit(@RequestBody InOrderPage inOrderPage) {
        // 字段校验
        Assert.isTrue(inOrderPage.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.CREATE_END.getCode()), "已发布的入库单不支持修改，请联系管理员！");
        this.checkColumns(inOrderPage);

        InOrder inOrder = new InOrder();
        BeanUtils.copyProperties(inOrderPage, inOrder);
        inOrderService.saveWarehousingEntry(inOrder, inOrderPage.getInOrderDetailList());
        return Result.OK();
    }

    /**
     * 字段校验
     *
     * @param inOrderPage
     */
    private void checkColumns(InOrderPage inOrderPage) {
        Assert.isTrue(StringUtils.isNotBlank(inOrderPage.getContainerNo()), "请输入集装箱号！");
        Assert.notNull(inOrderPage.getInOrderTime(), "请选择入库时间！");
        Assert.isTrue(StringUtils.isNotBlank(inOrderPage.getArea()), "请选择入库库区！");
        Assert.notNull(inOrderPage.getGoodsType(), "请选择品番类型！");
        Assert.notEmpty(inOrderPage.getInOrderDetailList(), "请填写入库明细！");
        long nullCount = inOrderPage.getInOrderDetailList().stream().filter(data -> StringUtils.isBlank(data.getSkuCode()) || data.getBoxNumber() == null).count();
        Assert.isTrue(nullCount == 0, "品番编码与入库数量必须填写！");

        QueryWrapper<InOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.ne(StringUtils.isNotBlank(inOrderPage.getId()), "id", inOrderPage.getId());
        queryWrapper.eq("in_order_code", inOrderPage.getInOrderCode());
        int repeatCount = inOrderService.count(queryWrapper);
        Assert.isTrue(repeatCount == 0, "订单号已存在！");
    }

    @AutoLog(value = "入库管理-发布入库单")
    @ApiOperation(value = "入库管理-发布入库单", notes = "编辑入库单，补充入库信息后发布，才能领取")
    @PostMapping("/release")
    public Result<?> release(@RequestParam String id) {
        inOrderService.releaseWarehousingEntry(id);
        return Result.OK();
    }

    @AutoLog(value = "入库管理-领取任务")
    @ApiOperation(value = "入库管理-领取任务", notes = "入库管理-领取任务")
    @PostMapping("/collect")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> collect(String id, String stationCode) {
        Assert.isTrue(StringUtils.isNotBlank(id), "请选择要领取的入库单任务！");
        // 获取当前登录人角色
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        SysRole sysRole = sysUserService.selectLoginUserRole();
        Assert.isTrue(Arrays.asList(WarehouseConstant.RoleCodeEnum.TALLY.getCode(), WarehouseConstant.RoleCodeEnum.DRIVER.getCode()).contains(sysRole.getRoleCode()), "您既不是理货员也不是叉车司机，无法领取任务！");

        // 校验当前登录人是否可领取入库单
        InOrder inOrder = inOrderService.getById(id);
        InOrderTaskMain inOrderTaskMain = new InOrderTaskMain();
        if (WarehouseConstant.RoleCodeEnum.TALLY.getCode().equals(sysRole.getRoleCode())) {
            Assert.isTrue(StringUtils.isNotBlank(stationCode), "请选择工作站后领取任务！");
            Assert.isTrue(Arrays.asList(WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY.getCode(), WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY_DRIVER.getCode()).contains(inOrder.getExecuteStaff()),
                    "当前入库单无需理货员处理！");

            inOrder.setTally(loginUser.getUsername());
            inOrder.setStationCode(stationCode);
            inOrder.setTallyReceive(new Date());

            inOrderTaskMain = inOrderTaskMainService.getOne(new QueryWrapper<InOrderTaskMain>().eq("in_order_id", inOrder.getId()).eq("execute_staff", WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY.getCode()));
        } else if (WarehouseConstant.RoleCodeEnum.DRIVER.getCode().equals(sysRole.getRoleCode())) {
            Assert.isTrue(Arrays.asList(WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode(), WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY_DRIVER.getCode()).contains(inOrder.getExecuteStaff()),
                    "当前入库单无需叉车司机处理！");

            inOrder.setDriver(loginUser.getUsername());
            inOrder.setDriverReceive(new Date());

            inOrderTaskMain = inOrderTaskMainService.getOne(new QueryWrapper<InOrderTaskMain>().eq("in_order_id", inOrder.getId()).eq("execute_staff", WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode()));
        }
        Assert.notNull(inOrderTaskMain.getId(), "入库任务信息异常，请联系管理员！");
        Assert.isFalse(inOrderTaskMain.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()), "入库任务已被【%s】领取，请勿重复操作！", inOrderTaskMain.getExecuteUserName());
        Assert.isFalse(inOrderTaskMain.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode()), "入库任务已被【%s】执行完成，请勿重复操作！", inOrderTaskMain.getExecuteUserName());
        Assert.isFalse(inOrderTaskMain.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.FORCE_END.getCode()), "入库任务已被强制结束，请勿重复操作！", inOrderTaskMain.getExecuteUserName());
        Assert.isFalse(inOrderTaskMain.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.DELETE.getCode()), "入库任务已被删除，不支持领取！");

        // 校验当前登录用户账号下是否有正在执行的任务。业务逻辑：用户账号不允许更改
        QueryWrapper<InOrderTaskMain> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("execute_user_name", loginUser.getUsername());
        queryWrapper.eq("status", WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode());
        int doingCount = inOrderTaskMainService.count(queryWrapper);
        Assert.isTrue(doingCount == 0, "您还有未执行完成的任务，请执行任务后再领取新的任务");

        // 领取任务 更新状态
        inOrder.setStatus(WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode());
        inOrderTaskMain.setExecuteUserName(loginUser.getUsername());
        inOrderTaskMain.setStatus(WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode());
        inOrderService.updateById(inOrder);
        inOrderTaskMainService.updateById(inOrderTaskMain);

        return Result.OK();
    }

    /**
     * 呼叫货架
     *
     * @param callShelvesVO
     * @return
     */
    @AutoLog(value = "入库管理-呼叫货架")
    @ApiOperation(value = "入库管理-呼叫货架", notes = "入库管理-呼叫货架")
    @PostMapping(value = "/callShelf")
    public Result<?> callShelf(@RequestBody CallShelvesVO callShelvesVO) {
        Assert.isTrue(StringUtils.isNotBlank(callShelvesVO.getStationCode()), "请选择工作站！");
        Assert.notEmpty(callShelvesVO.getShelfIdList(), "请选择要呼叫的货架！");
        Assert.isTrue(StringUtils.isNotBlank(callShelvesVO.getFace()), "请选择货架朝向！");

        String stationCode = callShelvesVO.getStationCode();
        String shelfFace = callShelvesVO.getFace();

        // 本次要呼叫的货架
        QueryWrapper<BasicShelf> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", callShelvesVO.getShelfIdList());
        List<BasicShelf> list = shelfService.list(queryWrapper);

        // 校验Redis工作站任务列表中是否存在当前货架
        for (BasicShelf basicShelf : list) {
            boolean hasKey = redisUtil.sHasKey(RedisUtil.TASK_LIST_KEY + stationCode, basicShelf.getShelfCode());
            Assert.isFalse(hasKey, "请勿重复呼叫货架【%s】", basicShelf.getShelfCode());
        }

        // 传递给RMS，参数为工作站，货架号
        for (BasicShelf basicShelf : list) {
            JSONObject jsonObject = rmsUtil.deliverShelfToStation(Integer.parseInt(stationCode), basicShelf.getShelfCode(), Collections.singletonList(shelfFace));
            // 判断请求结果
            int code = jsonObject.getJSONObject("response").getJSONObject("header").getIntValue("code");
            String msg = jsonObject.getJSONObject("response").getJSONObject("header").getString("msg");
            Assert.isTrue(code == 0, msg);

            // 获取 taskId
            int taskId = jsonObject.getJSONObject("response").getJSONObject("body").getIntValue("taskId");
            // 任务数据存入数据库(后续不查，这个表只用于备份调用记录)
            ShelfTask newShelfTask = new ShelfTask();
            newShelfTask.setTaskId(taskId);
            newShelfTask.setStationCode(stationCode);
            newShelfTask.setShelfCode(basicShelf.getShelfCode());
            newShelfTask.setShelfFace(shelfFace);
            shelfTaskService.save(newShelfTask);
            // 货架朝向存入Redis(String)
            boolean createFace = redisUtil.set(RedisUtil.STATION_CODE + stationCode + RedisUtil.FACE + basicShelf.getShelfCode(), shelfFace);
            Assert.isTrue(createFace, "保存货架朝向失败！请联系管理员！");
            // 货架编码存入Redis中的工作站任务列表中(Set)
            long createCount = redisUtil.sSet(RedisUtil.TASK_LIST_KEY + stationCode, basicShelf.getShelfCode());
            Assert.isTrue(createCount == 1, "保存任务失败！请联系管理员！");
        }

        return Result.OK("呼叫成功");
    }

    /**
     * 查询当前工作站的入库任务及任务货架
     *
     * @param stationCode
     * @return
     */
    @AutoLog(value = "入库操作-刷新当前货架")
    @ApiOperation(value = "入库操作-刷新当前货架", notes = "入库操作-刷新当前货架")
    @PostMapping(value = "/getInOrderShelf")
    public Result<?> getInOrderShelf(String stationCode) {
        Assert.isTrue(StringUtils.isNotBlank(stationCode), "缺少必要参数工作站编码！");

        // 通过Redis缓存获取货架朝向并调用RMS获取货架编码、任务信息
        ShelfTask shelfTask = this.getShelfTaskForRMS(stationCode);

        ShelfPTO response = new ShelfPTO();
        // 封装需要返回给前端的数据：货架朝向
        response.setShelfFace(shelfTask.getShelfFace());
        // 封装需要返回给前端的数据：货架对象
        BasicShelf shelfInfo = shelfService.getOne(new QueryWrapper<BasicShelf>().eq("shelf_code", shelfTask.getShelfCode()));
        response.setBasicShelf(shelfInfo);
        // 封装需要返回给前端的数据：货架类型对象
        BasicShelfType shelfTypeInfo = shelfTypeService.getOne(new QueryWrapper<BasicShelfType>().eq("shelf_code", shelfInfo.getShelfTypeCode()));
        response.setBasicShelfType(shelfTypeInfo);
        // 封装需要返回给前端的数据：货格集合
        List<BasicCompartment> compartmentList = basicCompartmentService.list(new QueryWrapper<BasicCompartment>()
                .eq("shelf_code", shelfTask.getShelfCode())
                .eq("is_two_sided", shelfTask.getShelfFace())
                .orderByAsc("compartment_code"));
        response.setCompartmentList(compartmentList);
        return Result.OK(response);
    }

    /**
     * 从RMS查询当前工作站的货架与任务信息
     *
     * @param stationCode
     * @return
     */
    public ShelfTask getShelfTaskForRMS(String stationCode) {
        // 查询工作站信息
        BasicStationInfo stationInfo = stationService.getOne(new QueryWrapper<BasicStationInfo>().eq("station_code", stationCode));

        // 查询当前工作站点位的货架编码是什么(接口不返回任务id)
        JSONObject shelfCodeResponse = rmsUtil.selectShelfCodeByStationPoint(Collections.singletonList(stationInfo.getStationPoint()));
        int shelfCodeResponseCode = shelfCodeResponse.getJSONObject("response").getJSONObject("header").getIntValue("code");
        String shelfCodeResponseMessage = shelfCodeResponse.getJSONObject("response").getJSONObject("header").getString("msg");
        Assert.isTrue(shelfCodeResponseCode == 0, "请等待货架在工作站时刷新");

        JSONArray shelfList = shelfCodeResponse.getJSONObject("response").getJSONObject("body").getJSONArray("shelves");
        Assert.notEmpty(shelfList, "未查询到工作站存在货架");
        JSONObject shelf = JSON.parseObject(JSONObject.toJSONString(shelfList.get(0)));
        // 当前位置的货架编码
        String shelfCode = shelf.getString("shelfCode");
        // 机器人id(货架在哪个机器人上)
        String robotId = shelf.getString("robotId");
        Assert.isTrue(StringUtils.isNotBlank(shelfCode), "未查询到工作站存在货架");
        Assert.isTrue(StringUtils.isNotBlank(robotId), "未查询到工作站存在机器人");

        // 由于上一个接口没有获取到任务id，所以要通过机器人id获取到任务id(用于后续转面和放行)
        JSONObject taskResponse = rmsUtil.selectTaskIdByRobotId(robotId);
        int taskResponseCode = taskResponse.getJSONObject("response").getJSONObject("header").getIntValue("code");
        String taskResponseMessage = taskResponse.getJSONObject("response").getJSONObject("header").getString("msg");
        Assert.isTrue(taskResponseCode == 0, taskResponseMessage);
        JSONArray robotList = taskResponse.getJSONObject("response").getJSONObject("body").getJSONArray("robots");
        Assert.notEmpty(robotList, "未查询到工作站存在机器人");
        JSONObject robot = JSON.parseObject(JSONObject.toJSONString(robotList.get(0)));
        // 任务id
        Integer taskId = robot.getInteger("taskId");
        Assert.notNull(taskId, "未查询到工作站存在机器人");

        // 获取货架朝向(通过Redis获取，呼叫货架时已存储货架的朝向)
        String shelfFace = (String) redisUtil.get(RedisUtil.STATION_CODE + stationCode + RedisUtil.FACE + shelfCode);
        Assert.notNull(shelfFace, "系统无货架朝向");

        // 将任务信息存入Redis中(String)
        ShelfTask shelfTask = new ShelfTask();
        shelfTask.setTaskId(taskId);
        shelfTask.setStationCode(stationCode);
        shelfTask.setShelfCode(shelfCode);
        shelfTask.setShelfFace(shelfFace);
        redisUtil.set(RedisUtil.DOING_TASK_KEY + stationCode, shelfTask);
        return shelfTask;
    }

    /**
     * 当前登录人正在执行的任务
     *
     * @return
     */
    @GetMapping("/getDoingOrder")
    public Result<?> getDoingOrder() {
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        QueryWrapper<InOrderTaskMain> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("execute_user_name", loginUser.getUsername());
        queryWrapper.eq("status", WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode());
        // 正常情况下 list中应有一条数据 如果没有数据代表当前登录人没有正在执行的任务，如果数据大于1条代表同时接了多个任务。
        List<InOrderTaskMain> list = inOrderTaskMainService.list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Result.OK(new InOrder());
        }

        InOrder inOrder = inOrderService.getById(list.get(0).getInOrderId());
        return Result.OK(inOrder);
    }

    /**
     * 理货员使用
     *
     * @param enterVo
     * @return
     */
    @AutoLog(value = "入库操作-理货员扫码入库")
    @ApiOperation(value = "入库操作-理货员扫码入库", notes = "入库操作-理货员扫码入库")
    @PostMapping(value = "/enter")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> enter(@RequestBody EnterVo enterVo) {
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        String labelNumber = enterVo.getLabelNumber();
        String compartmentCode = enterVo.getCompartmentCode();
        Assert.isTrue(StringUtils.isNotBlank(labelNumber), "请扫描正确的二维码");
        Assert.isTrue(StringUtils.isNotBlank(compartmentCode), "请扫描正确的货格编码");
        InOrder inOrder = inOrderService.getById(enterVo.getInOrder().getId());
        Assert.isFalse(Objects.equals(inOrder.getStatus(), WarehouseConstant.EnterWarehouseEnum.RELEASE_END.getCode()), "请领取任务后扫码入库");
        Assert.isFalse(Objects.equals(inOrder.getStatus(), WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode()), "任务已完成");
        Assert.isTrue(Objects.equals(inOrder.getStatus(), WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()), "入库中才可以扫码入库");

        // 获取本次要入库的品番
        String skuCode = null;
        List<InOrderDetail> inOrderDetailList = inOrderDetailService.list(new LambdaQueryWrapper<InOrderDetail>().eq(InOrderDetail::getInOrderId, inOrder.getId()));
        inOrderDetailList.sort(Comparator.comparingInt((InOrderDetail o) -> o.getSkuCode().length()).reversed());
        for (InOrderDetail inOrderDetail : inOrderDetailList) {
            if (labelNumber.contains(inOrderDetail.getSkuCode())) {
                skuCode = inOrderDetail.getSkuCode();
                break;
            }
        }
        Assert.notNull(skuCode, "请扫描当前任务中的品番");
        List<BasicSkuInfo> list = basicSkuInfoService.list(new LambdaQueryWrapper<BasicSkuInfo>().eq(BasicSkuInfo::getSkuCode, skuCode));
        BasicSkuInfo skuInfo = list.get(0);
        if (null == skuInfo.getLabelLength()) {
            skuInfo.setLabelLength(labelNumber.length());
            basicSkuInfoService.updateById(skuInfo);
        }
        Assert.isTrue(Objects.equals(labelNumber.length(), skuInfo.getLabelLength()) || labelNumber.length() < 50, "请扫描正确的二维码");
        if (Objects.equals(skuInfo.getSkuType(), WarehouseConstant.SkuTypeEnum.PT.getCode())) {
            int labelNumberCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>().eq(ReportLabel::getLabelNumber, enterVo.getLabelNumber()));
            Assert.isTrue(labelNumberCount == 0, "请勿重复扫描");
        }

        int otherOrderCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .eq(ReportLabel::getCompartmentCode, compartmentCode)
                .ne(ReportLabel::getInOrderId, inOrder.getId()));
        Assert.isTrue(otherOrderCount == 0, "货格被其它任务使用");
        int otherSkuCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .ne(ReportLabel::getSkuCode, skuCode)
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .eq(ReportLabel::getCompartmentCode, compartmentCode)
                .eq(ReportLabel::getInOrderId, inOrder.getId()));
        Assert.isTrue(otherSkuCount == 0, "货格已存储其它品番");
        int otherCompartmentCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(ReportLabel::getSkuCode, skuCode)
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .likeRight(ReportLabel::getCompartmentCode, compartmentCode.substring(0, 8) + "_" + compartmentCode.substring(9, 10))
                .ne(ReportLabel::getCompartmentCode, compartmentCode)
                .ne(ReportLabel::getInOrderId, inOrder.getId()));
        Assert.isTrue(otherCompartmentCount == 0, "请使用其它层的货格");

        // 校验本次要入库品番的箱数是否已达到任务的箱数
        InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                .eq(InOrderTaskMain::getExecuteUserName, loginUser.getUsername())
                .eq(InOrderTaskMain::getStatus, WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()));
        Assert.notNull(inOrderTaskMain, "您还未领取任务");
        Assert.isTrue(Objects.equals(inOrder.getId(), inOrderTaskMain.getInOrderId()), "请勿执行他人的入库任务！");
        // 求本次扫码的品番需要入库的数量
        List<InOrderTaskDetail> inOrderTaskDetailList = inOrderTaskDetailService.list(new LambdaQueryWrapper<InOrderTaskDetail>()
                .eq(InOrderTaskDetail::getInOrderTaskId, inOrderTaskMain.getId())
                .eq(InOrderTaskDetail::getSkuCode, skuCode));
        int needNumber = inOrderTaskDetailList.stream().mapToInt(InOrderTaskDetail::getBoxNumber).sum();
        // 求本次扫码的品番在当前订单下库存中的数量
        List<ReportLabel> reportLabelList = reportLabelService.list(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(ReportLabel::getSkuCode, skuCode)
                .eq(ReportLabel::getInOrderId, inOrder.getId()));
        int haveNumber = reportLabelList.stream().mapToInt(ReportLabel::getInventoryBoxQuantity).sum();
        Assert.isFalse(haveNumber >= needNumber, "任务已完成");
        // 需要入库数量 - 已经入库数量 = 1
        boolean enterEnd = needNumber - haveNumber == 1;

        // report_label AGV区数据存储逻辑：以货格与标签号作为唯一(AGV区一个货格内的订单号与品番编码都是相同的)
        ReportLabel reportLabel = reportLabelService.getOne(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(ReportLabel::getLabelNumber, labelNumber)
                .eq(ReportLabel::getCompartmentCode, compartmentCode));
        if (null == reportLabel) {
            reportLabel = new ReportLabel();
            reportLabel.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.AGV.getCode());
            reportLabel.setLabelNumber(labelNumber);
            reportLabel.setSkuCode(skuCode);
            reportLabel.setInventoryBoxQuantity(1);
            reportLabel.setInventoryGoodsQuantity(skuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(1);
            reportLabel.setAvailableGoodsQuantity(skuInfo.getBoxCapacity());
            reportLabel.setShelfCode(compartmentCode.substring(0, 8));
            reportLabel.setCompartmentCode(compartmentCode);
            reportLabel.setSkuType(inOrder.getGoodsType());
            reportLabel.setInOrderId(inOrder.getId());
            reportLabel.setInOrderCode(inOrder.getInOrderCode());
            reportLabel.setInDate(inOrder.getInOrderTime());
            reportLabel.setStackingDate(new Date());
            reportLabel.setContainerNo(inOrder.getContainerNo());
            reportLabel.setStackCode(null);
            reportLabel.setOutOrderId(null);
            reportLabelService.save(reportLabel);
        } else {
            reportLabel.setInventoryBoxQuantity(reportLabel.getInventoryBoxQuantity() + 1);
            reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() + skuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(reportLabel.getAvailableBoxQuantity() + 1);
            reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() + skuInfo.getBoxCapacity());
            reportLabelService.updateById(reportLabel);
        }

        // inventory_info AGV区数据存储逻辑：以货架编码、货格编码与品番编码作为唯一
        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, compartmentCode.substring(0, 8))
                .eq(InventoryInfo::getCompartmentCode, compartmentCode)
                .eq(InventoryInfo::getSkuCode, skuCode));
        if (null == inventoryInfo) {
            inventoryInfo = new InventoryInfo();
            inventoryInfo.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.AGV.getCode());
            inventoryInfo.setShelfCode(compartmentCode.substring(0, 8));
            inventoryInfo.setCompartmentCode(compartmentCode);
            inventoryInfo.setStackCode(null);
            inventoryInfo.setSkuCode(skuCode);
            inventoryInfo.setInventoryBoxQuantity(1);
            inventoryInfo.setInventoryGoodsQuantity(skuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(1);
            inventoryInfo.setAvailableGoodsQuantity(skuInfo.getBoxCapacity());
            inventoryInfo.setEnterDate(inOrder.getInOrderTime());
            inventoryInfo.setOutOrderId(null);
            inventoryInfoService.save(inventoryInfo);
        } else {
            inventoryInfo.setInventoryBoxQuantity(inventoryInfo.getInventoryBoxQuantity() + 1);
            inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() + skuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(inventoryInfo.getAvailableBoxQuantity() + 1);
            inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() + skuInfo.getBoxCapacity());
            inventoryInfoService.updateById(inventoryInfo);
        }

        // 删除理货区的库存
        InventoryInfo tallyInventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.PICK.getCode())
                .eq(InventoryInfo::getSkuCode, skuCode));
//        Assert.notNull(tallyInventoryInfo, "理货区库存数据异常，请联系管理员！");
        if (null != tallyInventoryInfo) { // 非空原因：存在没有经过理货 直接创建入库单
            tallyInventoryInfo.setInventoryBoxQuantity(tallyInventoryInfo.getInventoryBoxQuantity() - 1);
            tallyInventoryInfo.setInventoryGoodsQuantity(tallyInventoryInfo.getInventoryGoodsQuantity() - skuInfo.getBoxCapacity());
            if (tallyInventoryInfo.getInventoryBoxQuantity() == 0 && tallyInventoryInfo.getInventoryGoodsQuantity() == 0 && tallyInventoryInfo.getAvailableBoxQuantity() == 0 && tallyInventoryInfo.getAvailableGoodsQuantity() == 0) {
                inventoryInfoService.removeById(tallyInventoryInfo);
            } else {
                inventoryInfoService.updateById(tallyInventoryInfo);
            }
        }


        // 本次扫码的品番入齐了
        if (enterEnd) {
            /*
            由于现在不知道扫的是哪个货垛的码，无法精准更新库存，所以只能在这个品番都入库完成的时候 更新report_label中这个品番在这个订单号中所有的库存
            未来能拿到货垛码的时候，就可以每次扫码后将对应货垛、品番的数量-1了
             */
            reportLabelService.update(
                    new ReportLabel()
                            .setAvailableBoxQuantity(0)
                            .setAvailableGoodsQuantity(0),
                    new LambdaQueryWrapper<ReportLabel>()
                            .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.PICK.getCode())
                            .eq(ReportLabel::getSkuCode, skuCode)
                            .eq(ReportLabel::getInOrderCode, inOrder.getInOrderCode()));

            inOrderTaskDetailList.forEach(data -> data.setStatus(WarehouseConstant.EnterWarehouseDetailEnum.ENTER_END.getCode()));
            inOrderTaskDetailService.updateBatchById(inOrderTaskDetailList);

            // 查询一下未入库完的入库任务详情 如果全都入完了,更新入库任务状态
            int notEnterDetailCount = inOrderTaskDetailService.count(new LambdaQueryWrapper<InOrderTaskDetail>()
                    .eq(InOrderTaskDetail::getInOrderTaskId, inOrderTaskMain.getId())
                    .eq(InOrderTaskDetail::getStatus, WarehouseConstant.EnterWarehouseDetailEnum.ENTER_NOT.getCode()));
            if (notEnterDetailCount == 0) {
                // 所有详情都入库完成,更新入库任务为执行完成状态
                inOrderTaskMain.setStatus(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode());
                inOrderTaskMainService.updateById(inOrderTaskMain);

                // 查询入库任务未完成的数量,如果全都完成了,更新入库单状态
                int notEnterMainCount = inOrderTaskMainService.count(new LambdaQueryWrapper<InOrderTaskMain>()
                        .eq(InOrderTaskMain::getInOrderId, inOrder.getId())
                        .in(InOrderTaskMain::getStatus, Arrays.asList(WarehouseConstant.EnterWarehouseEnum.RELEASE_END.getCode(), WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode())));
                if (notEnterMainCount == 0) {
                    // 入库单对应的入库任务全部已完成
                    inOrder.setTallyTime(new Date());
                    inOrder.setStatus(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode());
                    inOrderService.updateById(inOrder);
                }
            }
        }

        return Result.OK();
    }

    /**
     * 已入库数量-1
     *
     * @param id
     * @return
     */
    @AutoLog(value = "入库操作-更新入库数量")
    @ApiOperation(value = "入库操作-更新入库数量", notes = "入库操作-更新入库数量")
    @PostMapping("/reduceInventory")
    public Result<?> reduceInventory(@NotNull String id) {
        ReportLabel reportLabel = reportLabelService.getById(id);
        Assert.isTrue(reportLabel.getAvailableBoxQuantity() > 0, "库存可用数量为0，无法变更");

        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                .eq(InOrderTaskMain::getExecuteUserName, loginUser.getUsername())
                .eq(InOrderTaskMain::getStatus, WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()));
        Assert.isTrue(null != inOrderTaskMain && Objects.equals(reportLabel.getInOrderId(), inOrderTaskMain.getInOrderId()),
                "请不要变更他人入库的库存！");

        InOrder inOrder = inOrderService.getById(reportLabel.getInOrderId());
        Assert.isFalse(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode()), "任务完成后不可变更");
        Assert.isFalse(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.FORCE_END.getCode()), "任务完成后不可变更");

        BasicSkuInfo skuInfo = basicSkuInfoService.list(new LambdaQueryWrapper<BasicSkuInfo>().eq(BasicSkuInfo::getSkuCode, reportLabel.getSkuCode())).get(0);

        // report_label 库存-1
        if (reportLabel.getInventoryBoxQuantity() - 1 == 0 && reportLabel.getAvailableBoxQuantity() - 1 == 0) {
            reportLabelService.removeById(reportLabel.getId());
        } else {
            reportLabel.setInventoryBoxQuantity(reportLabel.getInventoryBoxQuantity() - 1);
            reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() - skuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(reportLabel.getAvailableBoxQuantity() - 1);
            reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() - skuInfo.getBoxCapacity());
            reportLabelService.updateById(reportLabel);
        }


        // inventory_info AGV区数据存储逻辑：以货架编码、货格编码与品番编码作为唯一
        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, reportLabel.getShelfCode())
                .eq(InventoryInfo::getCompartmentCode, reportLabel.getCompartmentCode())
                .eq(InventoryInfo::getSkuCode, reportLabel.getSkuCode()));
        if (inventoryInfo.getInventoryBoxQuantity() - 1 == 0 && inventoryInfo.getAvailableBoxQuantity() - 1 == 0) {
            inventoryInfoService.removeById(inventoryInfo.getId());
        } else {
            inventoryInfo.setInventoryBoxQuantity(inventoryInfo.getInventoryBoxQuantity() - 1);
            inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() - skuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(inventoryInfo.getAvailableBoxQuantity() - 1);
            inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() - skuInfo.getBoxCapacity());
            inventoryInfoService.updateById(inventoryInfo);
        }


        return Result.OK();
    }

    /**
     * 入库时更新库存的件数
     *
     * @param reportLabelId
     * @return
     */
    @AutoLog(value = "入库操作-更新入库数量")
    @ApiOperation(value = "入库操作-更新入库数量", notes = "入库操作-更新入库数量")
    @PostMapping("/updateGoodsQuantity")
    public Result<?> updateGoodsQuantity(@RequestParam(name = "id") String reportLabelId, @RequestParam(name = "goodsQuantity") Integer goodsQuantity) {
        ReportLabel reportLabel = reportLabelService.getById(reportLabelId);
        Assert.isTrue(reportLabel.getAvailableBoxQuantity() > 0, "库存可用数量为0，无法变更");

        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                .eq(InOrderTaskMain::getExecuteUserName, loginUser.getUsername())
                .eq(InOrderTaskMain::getStatus, WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()));
        Assert.isTrue(null != inOrderTaskMain && Objects.equals(reportLabel.getInOrderId(), inOrderTaskMain.getInOrderId()),
                "不可以更新其它入库单入库的数据！");

        InOrder inOrder = inOrderService.getById(reportLabel.getInOrderId());
        Assert.notNull(inOrder, "入库单信息异常，请联系管理员！");
        Assert.isFalse(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode()), "任务完成后不可变更");
        Assert.isFalse(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.FORCE_END.getCode()), "任务完成后不可变更");

        Integer addGoodsQuantity = goodsQuantity - reportLabel.getAvailableGoodsQuantity();

        reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() + addGoodsQuantity);
        reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() + addGoodsQuantity);
        reportLabelService.updateById(reportLabel);

        // inventory_info AGV区数据存储逻辑：以货架编码、货格编码与品番编码作为唯一
        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, reportLabel.getShelfCode())
                .eq(InventoryInfo::getCompartmentCode, reportLabel.getCompartmentCode())
                .eq(InventoryInfo::getSkuCode, reportLabel.getSkuCode()));
        inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() + addGoodsQuantity);
        inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() + addGoodsQuantity);
        inventoryInfoService.updateById(inventoryInfo);

        return Result.OK();
    }

    @AutoLog(value = "入库操作-扫码入库(无单扫码)")
    @ApiOperation(value = "入库操作-扫码入库(无单扫码)", notes = "入库操作-扫码入库(无单扫码)")
    @PostMapping("/noOrderScanCode")
    public Result<?> noOrderScanCode(@RequestParam(name = "labelNumber", required = true) String labelNumber,
                                     @RequestParam(name = "compartmentCode", required = true) String compartmentCode) {
        List<BasicSkuInfo> basicSkuInfoList = basicSkuInfoService.list();
        Set<String> skuCodeSet = basicSkuInfoList.stream()
                .map(BasicSkuInfo::getSkuCode)
                .filter(labelNumber::contains)
                .collect(Collectors.toSet());
        Assert.notEmpty(skuCodeSet, "品番未维护至系统");
        List<String> sortedSkuCodeList = skuCodeSet.stream()
                .sorted(Comparator.comparingInt(String::length).reversed())
                .collect(Collectors.toList());
        String skuCode = sortedSkuCodeList.get(0);
        BasicSkuInfo basicSkuInfo = basicSkuInfoList
                .stream()
                .filter(data -> Objects.equals(data.getSkuCode(), skuCode))
                .collect(Collectors.toList())
                .get(0);
        if (Objects.equals(basicSkuInfo.getSkuType(), WarehouseConstant.SkuTypeEnum.PT.getCode())){
            int labelNumberQuantity = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                    .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                    .eq(ReportLabel::getLabelNumber, labelNumber));
            Assert.isTrue(labelNumberQuantity == 0, "请勿重复扫描！");
        }
        int otherOrderCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .eq(ReportLabel::getCompartmentCode, compartmentCode)
                .isNotNull(ReportLabel::getInOrderId));
        Assert.isTrue(otherOrderCount == 0, "货格被其它任务使用");
        int otherSkuCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .ne(ReportLabel::getSkuCode, skuCode)
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .eq(ReportLabel::getCompartmentCode, compartmentCode));
        Assert.isTrue(otherSkuCount == 0, "货格已存储其它品番");
        int otherCompartmentCount = reportLabelService.count(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(ReportLabel::getSkuCode, skuCode)
                .gt(ReportLabel::getAvailableBoxQuantity, 0)
                .likeRight(ReportLabel::getCompartmentCode, compartmentCode.substring(0, 8) + "_" + compartmentCode.substring(9, 10))
                .ne(ReportLabel::getCompartmentCode, compartmentCode));
        Assert.isTrue(otherCompartmentCount == 0, "请使用其它层的货格");

        // report_label AGV区数据存储逻辑：以货格与标签号作为唯一(AGV区一个货格内的订单号与品番编码都是相同的)
        ReportLabel reportLabel = reportLabelService.getOne(new LambdaQueryWrapper<ReportLabel>()
                .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(ReportLabel::getLabelNumber, labelNumber)
                .eq(ReportLabel::getCompartmentCode, compartmentCode));
        if (null == reportLabel) {
            reportLabel = new ReportLabel();
            reportLabel.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.AGV.getCode());
            reportLabel.setLabelNumber(labelNumber);
            reportLabel.setSkuCode(skuCode);
            reportLabel.setInventoryBoxQuantity(1);
            reportLabel.setInventoryGoodsQuantity(basicSkuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(1);
            reportLabel.setAvailableGoodsQuantity(basicSkuInfo.getBoxCapacity());
            reportLabel.setShelfCode(compartmentCode.substring(0, 8));
            reportLabel.setCompartmentCode(compartmentCode);
            reportLabel.setSkuType(basicSkuInfo.getSkuType());
            reportLabel.setInDate(new Date());
            reportLabel.setStackingDate(new Date());
            reportLabel.setContainerNo("无单上架");
            reportLabelService.save(reportLabel);
        } else {
            reportLabel.setInventoryBoxQuantity(reportLabel.getInventoryBoxQuantity() + 1);
            reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() + basicSkuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(reportLabel.getAvailableBoxQuantity() + 1);
            reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() + basicSkuInfo.getBoxCapacity());
            reportLabelService.updateById(reportLabel);
        }

        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, compartmentCode.substring(0, 8))
                .eq(InventoryInfo::getCompartmentCode, compartmentCode)
                .eq(InventoryInfo::getSkuCode, skuCode));
        if (null == inventoryInfo) {
            inventoryInfo = new InventoryInfo();
            inventoryInfo.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.AGV.getCode());
            inventoryInfo.setShelfCode(compartmentCode.substring(0, 8));
            inventoryInfo.setCompartmentCode(compartmentCode);
            inventoryInfo.setSkuCode(skuCode);
            inventoryInfo.setInventoryBoxQuantity(1);
            inventoryInfo.setInventoryGoodsQuantity(basicSkuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(1);
            inventoryInfo.setAvailableGoodsQuantity(basicSkuInfo.getBoxCapacity());
            inventoryInfo.setEnterDate(new Date());
            inventoryInfoService.save(inventoryInfo);
        } else {
            inventoryInfo.setInventoryBoxQuantity(inventoryInfo.getInventoryBoxQuantity() + 1);
            inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() + basicSkuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(inventoryInfo.getAvailableBoxQuantity() + 1);
            inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() + basicSkuInfo.getBoxCapacity());
            inventoryInfoService.updateById(inventoryInfo);
        }

        return Result.OK();
    }

    @AutoLog(value = "入库操作-更新入库数量(无单扫码)")
    @ApiOperation(value = "入库操作-更新入库数量(无单扫码)", notes = "入库操作-更新入库数量(无单扫码)")
    @PostMapping("/noOrderReduceInventory")
    public Result<?> noOrderReduceInventory(@RequestParam(name = "id", required = true) String id) {
        ReportLabel reportLabel = reportLabelService.getById(id);
        BasicSkuInfo basicSkuInfo = basicSkuInfoService.list(new LambdaQueryWrapper<BasicSkuInfo>().eq(BasicSkuInfo::getSkuCode, reportLabel.getSkuCode())).get(0);

        if (reportLabel.getInventoryBoxQuantity() - 1 == 0 && reportLabel.getAvailableBoxQuantity() - 1 == 0) {
            reportLabelService.removeById(id);
        } else {
            reportLabel.setInventoryBoxQuantity(reportLabel.getInventoryBoxQuantity() - 1);
            reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() - basicSkuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(reportLabel.getAvailableBoxQuantity() - 1);
            reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() - basicSkuInfo.getBoxCapacity());
            reportLabelService.updateById(reportLabel);
        }

        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, reportLabel.getShelfCode())
                .eq(InventoryInfo::getCompartmentCode, reportLabel.getCompartmentCode())
                .eq(InventoryInfo::getSkuCode, reportLabel.getSkuCode()));
        if (inventoryInfo.getInventoryBoxQuantity() - 1 == 0 && inventoryInfo.getAvailableBoxQuantity() - 1 == 0) {
            inventoryInfoService.removeById(inventoryInfo.getId());
        } else {
            inventoryInfo.setInventoryBoxQuantity(inventoryInfo.getInventoryBoxQuantity() - 1);
            inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() - basicSkuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(inventoryInfo.getAvailableBoxQuantity() - 1);
            inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() - basicSkuInfo.getBoxCapacity());
            inventoryInfoService.updateById(inventoryInfo);
        }

        return Result.OK();
    }

    @AutoLog(value = "入库操作-更新入库数量(无单扫码)")
    @ApiOperation(value = "入库操作-更新入库数量(无单扫码)", notes = "入库操作-更新入库数量(无单扫码)")
    @PostMapping("/noOrderUpdateGoodsQuantity")
    public Result<?> noOrderUpdateGoodsQuantity(@RequestParam(name = "id", required = true) String id,
                                                @RequestParam(name = "goodsQuantity", required = true) Integer goodsQuantity) {
        ReportLabel reportLabel = reportLabelService.getById(id);
        Integer addGoodsQuantity = goodsQuantity - reportLabel.getAvailableGoodsQuantity();

        reportLabel.setInventoryGoodsQuantity(reportLabel.getInventoryGoodsQuantity() + addGoodsQuantity);
        reportLabel.setAvailableGoodsQuantity(reportLabel.getAvailableGoodsQuantity() + addGoodsQuantity);
        reportLabelService.updateById(reportLabel);
        InventoryInfo inventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.AGV.getCode())
                .eq(InventoryInfo::getShelfCode, reportLabel.getShelfCode())
                .eq(InventoryInfo::getCompartmentCode, reportLabel.getCompartmentCode())
                .eq(InventoryInfo::getSkuCode, reportLabel.getSkuCode()));
        inventoryInfo.setInventoryGoodsQuantity(inventoryInfo.getInventoryGoodsQuantity() + addGoodsQuantity);
        inventoryInfo.setAvailableGoodsQuantity(inventoryInfo.getAvailableGoodsQuantity() + addGoodsQuantity);
        inventoryInfoService.updateById(inventoryInfo);

        return Result.OK();
    }

    /**
     * 叉车司机
     *
     * @param
     * @return
     */
    @AutoLog(value = "入库操作-叉车司机获取任务详情")
    @ApiOperation(value = "入库操作-叉车司机获取任务详情", notes = "入库操作-叉车司机获取任务详情")
    @PostMapping(value = "/getDriverTaskDetailList")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> getDriverTaskDetailList(@RequestParam(name = "id") String inOrderId) {
        InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                .eq(InOrderTaskMain::getInOrderId, inOrderId)
                .eq(InOrderTaskMain::getExecuteStaff, WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode()));
        Assert.notNull(inOrderTaskMain, "任务数据异常，请联系管理员！");
        // 求本次扫码的品番需要入库的数量
        List<InOrderTaskDetail> inOrderTaskDetailList = inOrderTaskDetailService.list(new LambdaQueryWrapper<InOrderTaskDetail>().eq(InOrderTaskDetail::getInOrderTaskId, inOrderTaskMain.getId()));
        inOrderTaskDetailList = inOrderTaskDetailList.stream().sorted(Comparator.comparing(InOrderTaskDetail::getStatus)).collect(Collectors.toList());
        return Result.OK(inOrderTaskDetailList);
    }

    /**
     * 叉车司机
     *
     * @param
     * @return
     */
    @AutoLog(value = "入库操作-叉车司机扫码入库")
    @ApiOperation(value = "入库操作-叉车司机扫码入库", notes = "入库操作-叉车司机扫码入库")
    @PostMapping(value = "/driverScanQRCode")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> driverScanQRCode(@RequestBody JSONObject jsonObject) {
        String inOrderId = jsonObject.getString("inOrderId");
        String labelNumber = jsonObject.getString("labelNumber");
        String comportmentCode = jsonObject.getString("comportmentCode");
        Assert.isTrue(StringUtils.isNotBlank(inOrderId), "缺少必要参数inOrderId！");
        Assert.isTrue(StringUtils.isNotBlank(labelNumber), "缺少必要参数labelNumber！");
        Assert.isTrue(StringUtils.isNotBlank(comportmentCode), "缺少必要参数comportmentCode！");

        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        InOrder inOrder = inOrderService.getById(inOrderId);
        Assert.notNull(inOrder, "入库单数据异常，请联系管理员！");
        Assert.isTrue(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()), "入库单非进行中状态，无法入库，请联系管理员！");
        List<InOrderDetail> inOrderDetailList = inOrderDetailService.list(new LambdaQueryWrapper<InOrderDetail>().eq(InOrderDetail::getInOrderId, inOrder.getId()));

        // 获取本次要入库的品番
        String stackCode = null;
        Assert.notEmpty(inOrderDetailList, "入库单详情数据异常，请联系管理员！");
        for (InOrderDetail inOrderDetail : inOrderDetailList) {
            if (labelNumber.contains(inOrderDetail.getStackCode())) {
                stackCode = inOrderDetail.getStackCode();
            }
        }
        Assert.notNull(stackCode, "扫描的二维码不是当前入库任务中的货垛号码，请检查后重试！");

        InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                .eq(InOrderTaskMain::getExecuteUserName, loginUser.getUsername())
                .eq(InOrderTaskMain::getStatus, WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()));
        Assert.notNull(inOrderTaskMain, "您还没有领取入库任务，无法进行入库！");
        Assert.isTrue(Objects.equals(inOrderId, inOrderTaskMain.getInOrderId()), "请勿执行他人的入库任务！");

        // 当前扫码货垛的任务详情
        List<InOrderTaskDetail> inOrderTaskDetailList = inOrderTaskDetailService.list(new LambdaQueryWrapper<InOrderTaskDetail>()
                .eq(InOrderTaskDetail::getInOrderTaskId, inOrderTaskMain.getId())
                .eq(InOrderTaskDetail::getStackCode, stackCode));
        Assert.notEmpty(inOrderTaskDetailList, "查询当前入库任务中无需入库该货垛！");
        // 过滤出本次扫码入库的货垛码中的品番
        long enterEndCount = inOrderTaskDetailList.stream().filter(data -> Objects.equals(data.getStatus(), WarehouseConstant.EnterWarehouseDetailEnum.ENTER_END.getCode())).count();
        Assert.isTrue(enterEndCount == 0, "该货垛已入库完成，请勿重复入库！");

        /*
        inventory_info 高位库区数据存储逻辑：以货格编码、货垛码与品番编码作为唯一
        由于后续出库时需要先入先出逻辑，并且货垛码不能保证全局唯一，所以一个货格内不能存放不同订单的相同货垛
         */
        int count = inventoryInfoService.count(new LambdaQueryWrapper<InventoryInfo>()
                .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.MULTILAYER.getCode())
                .eq(InventoryInfo::getCompartmentCode, comportmentCode)
                .eq(InventoryInfo::getStackCode, stackCode));
        Assert.isTrue(count == 0, "该货格无法存放该货垛，请更换货格后重试！");

        Map<String, List<InOrderTaskDetail>> enterMap = inOrderTaskDetailList.stream().collect(Collectors.groupingBy(InOrderTaskDetail::getSkuCode));
        for (String skuCode : enterMap.keySet()) {
            BasicSkuInfo skuInfo = basicSkuInfoService.list(new LambdaQueryWrapper<BasicSkuInfo>().eq(BasicSkuInfo::getSkuCode, skuCode)).get(0);

            // 货垛内当前品番的总箱数
            List<InOrderTaskDetail> enterTaskDetailList = enterMap.get(skuCode);
            int boxNumber = enterTaskDetailList.stream().mapToInt(InOrderTaskDetail::getBoxNumber).sum();

            // inventory_info 高位库区数据存储逻辑：以货格编码、货垛码与品番编码作为唯一(不存储货架编码)。
            InventoryInfo inventoryInfo = new InventoryInfo();
            inventoryInfo.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.MULTILAYER.getCode());
            inventoryInfo.setShelfCode(comportmentCode);
            inventoryInfo.setCompartmentCode(comportmentCode);
            inventoryInfo.setStackCode(stackCode);
            inventoryInfo.setSkuCode(skuCode);
            inventoryInfo.setInventoryBoxQuantity(boxNumber);
            inventoryInfo.setInventoryGoodsQuantity(boxNumber * skuInfo.getBoxCapacity());
            inventoryInfo.setAvailableBoxQuantity(boxNumber);
            inventoryInfo.setAvailableGoodsQuantity(boxNumber * skuInfo.getBoxCapacity());
            inventoryInfo.setEnterDate(new Date());
            inventoryInfo.setOutOrderId(null);
            inventoryInfoService.save(inventoryInfo);

            // report_label 高位库区数据存储逻辑：以货格编码、货垛码与品番编码作为唯一(标签号与货架编码不参与业务逻辑)。
            ReportLabel reportLabel = new ReportLabel();
            reportLabel.setWarehouseCode(WarehouseConstant.WarehouseTypeEnum.MULTILAYER.getCode());
            reportLabel.setLabelNumber(labelNumber);
            reportLabel.setSkuCode(skuCode);
            reportLabel.setInventoryBoxQuantity(boxNumber);
            reportLabel.setInventoryGoodsQuantity(boxNumber * skuInfo.getBoxCapacity());
            reportLabel.setAvailableBoxQuantity(boxNumber);
            reportLabel.setAvailableGoodsQuantity(boxNumber * skuInfo.getBoxCapacity());
            reportLabel.setShelfCode(comportmentCode);
            reportLabel.setCompartmentCode(comportmentCode);
            reportLabel.setSkuType(skuInfo.getSkuType());
            reportLabel.setInOrderId(inOrderId);
            reportLabel.setInOrderCode(inOrder.getInOrderCode());
            reportLabel.setInDate(inOrder.getInOrderTime());
            reportLabel.setStackingDate(new Date());
            reportLabel.setContainerNo(inOrder.getContainerNo());
            reportLabel.setStackCode(stackCode);
            reportLabel.setOutOrderId(null);
            reportLabelService.save(reportLabel);

            // report_label 理货区数据存储逻辑：以理货订单号、货垛码、品番编码作为唯一(不存储标签号、货架编码与货格编码)。
            reportLabelService.update(
                    new ReportLabel()
                            .setAvailableBoxQuantity(0)
                            .setAvailableGoodsQuantity(0),
                    new LambdaQueryWrapper<ReportLabel>()
                            .eq(ReportLabel::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.PICK.getCode())
                            .eq(ReportLabel::getSkuCode, skuCode)
                            .eq(ReportLabel::getStackCode, stackCode)
                            .eq(ReportLabel::getInOrderCode, inOrder.getInOrderCode()));

            // inventory_info 理货区数据存储逻辑：以品番编码作为唯一(不存储货架编码、货格编码与货垛码)。
            InventoryInfo pickInventoryInfo = inventoryInfoService.getOne(new LambdaQueryWrapper<InventoryInfo>()
                    .eq(InventoryInfo::getWarehouseCode, WarehouseConstant.WarehouseTypeEnum.PICK.getCode())
                    .eq(InventoryInfo::getSkuCode, skuCode));
            if (null != pickInventoryInfo) {
                Assert.isTrue(pickInventoryInfo.getInventoryBoxQuantity() >= boxNumber,
                        "品番编码：【%s】在理货区剩余库存箱数【%s】箱，本次需入库【%s】箱，库存不足，无法入库！", skuCode, pickInventoryInfo.getInventoryBoxQuantity(), boxNumber);
                pickInventoryInfo.setInventoryBoxQuantity(pickInventoryInfo.getInventoryBoxQuantity() - boxNumber);
                pickInventoryInfo.setInventoryGoodsQuantity(pickInventoryInfo.getInventoryGoodsQuantity() - boxNumber * skuInfo.getBoxCapacity());
                if (pickInventoryInfo.getInventoryBoxQuantity() == 0 && pickInventoryInfo.getInventoryGoodsQuantity() == 0 && pickInventoryInfo.getAvailableBoxQuantity() == 0 && pickInventoryInfo.getAvailableGoodsQuantity() == 0) {
                    inventoryInfoService.removeById(pickInventoryInfo.getId());
                } else {
                    inventoryInfoService.updateById(pickInventoryInfo);
                }
            }

            // 更新任务的状态
            enterTaskDetailList.forEach(data -> data.setStatus(WarehouseConstant.EnterWarehouseDetailEnum.ENTER_END.getCode()));
            inOrderTaskDetailService.updateBatchById(enterTaskDetailList);
        }

        // 校验所有任务是否完成
        int notEnterTaskDetailCount = inOrderTaskDetailService.count(new LambdaQueryWrapper<InOrderTaskDetail>()
                .eq(InOrderTaskDetail::getInOrderTaskId, inOrderTaskMain.getId())
                .eq(InOrderTaskDetail::getStatus, WarehouseConstant.EnterWarehouseDetailEnum.ENTER_NOT.getCode()));
        if (notEnterTaskDetailCount == 0) {
            // 当前登录人的任务完成了 更新任务主表状态、更新入库单上架完成时间
            inOrderTaskMain.setStatus(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode());
            inOrderTaskMainService.updateById(inOrderTaskMain);
            inOrder.setDriverTime(new Date());

            // 这个入库单未完成的任务数量
            int notEnterTaskMainCount = inOrderTaskMainService.count(new LambdaQueryWrapper<InOrderTaskMain>()
                    .eq(InOrderTaskMain::getInOrderId, inOrderId)
                    .in(InOrderTaskMain::getStatus, Arrays.asList(WarehouseConstant.EnterWarehouseEnum.RELEASE_END.getCode(), WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode())));
            if (notEnterTaskMainCount == 0) {
                inOrder.setStatus(WarehouseConstant.EnterWarehouseEnum.EXECUTE_END.getCode());
            }
            inOrderService.updateById(inOrder);
        }
        return Result.OK(inOrderTaskMain.getStatus());
    }

    /**
     * 货架转面
     *
     * @param stationCode 工作站编码
     * @param shelfFace   货架朝向(货架要转到哪一面：F-正面；B-反面)
     * @return
     */
    @AutoLog(value = "入库操作-货架转面")
    @ApiOperation(value = "入库操作-货架转面", notes = "入库操作-货架转面")
    @GetMapping("/rotate/{stationCode}/{shelfFace}")
    public Result<?> rotate(@PathVariable String stationCode, @PathVariable String shelfFace) {
        Assert.isTrue(StringUtils.isNotBlank(stationCode), "缺少必要参数工作站编码！");
        Assert.isTrue(StringUtils.isNotBlank(shelfFace), "缺少必要参数货架朝向！");

        // 通过Redis缓存获取货架朝向、货架编码、任务信息
        Object shelfTaskObject = redisUtil.get(RedisUtil.DOING_TASK_KEY + stationCode);
        ShelfTask shelfTask = (ShelfTask) shelfTaskObject;
        // 如果缓存中没有任务数据，通过Redis缓存获取货架朝向并调用RMS获取货架编码、任务信息
        if (null == shelfTask) {
            shelfTask = this.getShelfTaskForRMS(stationCode);
        }

        // 调用RMS命令货架转面
        JSONObject jsonObject = rmsUtil.deliverShelfToTurn(shelfTask.getTaskId(), Collections.singletonList(shelfFace));
        int code = jsonObject.getJSONObject("response").getJSONObject("header").getIntValue("code");
        String msg = jsonObject.getJSONObject("response").getJSONObject("header").getString("msg");
        Assert.isTrue(code == 0, msg);
        boolean createFace = redisUtil.set(RedisUtil.STATION_CODE + stationCode + RedisUtil.FACE + shelfTask.getShelfCode(), shelfFace);
        Assert.isTrue(createFace, "保存货架朝向失败！请联系管理员！");

        return Result.OK();
    }

    /**
     * 货架放行
     *
     * @param stationCode 工作站编码
     * @return
     */
    @AutoLog(value = "入库操作-货架放行")
    @ApiOperation(value = "入库操作-货架放行", notes = "入库操作-货架放行")
    @GetMapping("/releaseShelf/{stationCode}")
    public Result<?> releaseShelf(@PathVariable("stationCode") String stationCode) {
        Assert.isTrue(StringUtils.isNotBlank(stationCode), "缺少必要参数工作站编码！");

        // 获取正在执行的任务
        Object shelfTaskObject = redisUtil.get(RedisUtil.DOING_TASK_KEY + stationCode);
        ShelfTask shelfTask = (ShelfTask) shelfTaskObject;
        Assert.notNull(shelfTask, "货架已放行，请勿重复操作！");

        // 调用RMS放行
        JSONObject jsonObject = rmsUtil.deliverShelfToPlacement(shelfTask.getTaskId());
        int code = jsonObject.getJSONObject("response").getJSONObject("header").getIntValue("code");
        String msg = jsonObject.getJSONObject("response").getJSONObject("header").getString("msg");
        Assert.isTrue(code == 0, msg);

        // 任务已执行完毕 删除当前任务、删除货架朝向
        redisUtil.del(RedisUtil.DOING_TASK_KEY + stationCode, RedisUtil.STATION_CODE + stationCode + RedisUtil.FACE + shelfTask.getShelfCode());
        // 任务已执行完毕 删除工作站任务列表中的该货架编码
        redisUtil.setRemove(RedisUtil.TASK_LIST_KEY + stationCode, shelfTask.getShelfCode());

        return Result.OK();
    }

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    @AutoLog(value = "理货管理-通过id查询")
    @ApiOperation(value = "理货管理-通过id查询", notes = "理货管理-通过id查询")
    @GetMapping(value = "/queryById")
    public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
        InOrder inOrder = inOrderService.getById(id);
        if (inOrder == null) {
            return Result.error("未找到对应数据");
        }

        SysRole sysRole = sysUserService.selectLoginUserRole();
        if (Objects.equals(WarehouseConstant.RoleCodeEnum.TALLY.getCode(), sysRole.getRoleCode())) {
            InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                    .eq(InOrderTaskMain::getInOrderId, inOrder.getId())
                    .eq(InOrderTaskMain::getExecuteStaff, WarehouseConstant.EnterWarehouseTaskStaffEnum.TALLY.getCode()));
            inOrder.setTaskStatus(null == inOrderTaskMain ? inOrder.getStatus() : inOrderTaskMain.getStatus());
        } else if (Objects.equals(WarehouseConstant.RoleCodeEnum.DRIVER.getCode(), sysRole.getRoleCode())) {
            InOrderTaskMain inOrderTaskMain = inOrderTaskMainService.getOne(new LambdaQueryWrapper<InOrderTaskMain>()
                    .eq(InOrderTaskMain::getInOrderId, inOrder.getId())
                    .eq(InOrderTaskMain::getExecuteStaff, WarehouseConstant.EnterWarehouseTaskStaffEnum.DRIVER.getCode()));
            inOrder.setTaskStatus(null == inOrderTaskMain ? inOrder.getStatus() : inOrderTaskMain.getStatus());
        } else {
            inOrder.setTaskStatus(inOrder.getStatus());
        }
        return Result.OK(inOrder);
    }

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    @AutoLog(value = "入库管理-查询入库单明细")
    @ApiOperation(value = "入库管理-查询入库单明细", notes = "入库管理-查询入库单明细")
    @GetMapping(value = "/queryInOrderDetailByMainId")
    public Result<?> queryInOrderDetailListByMainId(@RequestParam(name = "id", required = true) String id) {
        List<InOrderDetail> inOrderDetailList = inOrderDetailService.list(new LambdaQueryWrapper<InOrderDetail>().eq(InOrderDetail::getInOrderId, id));
        return Result.OK(inOrderDetailList);
    }

    /**
     * 通过id删除
     *
     * @param id
     * @return
     */
    @AutoLog(value = "入库管理-通过id删除")
    @ApiOperation(value = "入库管理-通过id删除", notes = "入库管理-通过id删除")
    @DeleteMapping(value = "/delete")
    public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
        inOrderService.delMain(id);
        return Result.OK("删除成功!");
    }

    /**
     * 批量删除
     *
     * @param ids ids
     * @return 响应
     */
    @AutoLog(value = "入库管理-批量删除")
    @ApiOperation(value = "入库管理-批量删除", notes = "入库管理-批量删除")
    @DeleteMapping(value = "/deleteBatch")
    public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
        this.inOrderService.delBatchMain(Arrays.asList(ids.split(",")));
        return Result.OK("批量删除成功!");
    }

    /**
     * 强制结束入库单
     * 用于入库数量不够时强制结束
     *
     * @param id
     * @return 响应
     */
    @AutoLog(value = "入库管理-强制结束入库单")
    @ApiOperation(value = "入库管理-强制结束入库单", notes = "入库管理-强制结束入库单")
    @DeleteMapping(value = "/forceEnd")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> forceEnd(String id, String remarks) {
        Assert.isTrue(StringUtils.isNotBlank(id), "缺少必要参数id！");
        Assert.isTrue(StringUtils.isNotBlank(remarks), "请输入强制结束原因！");
        InOrder inOrder = inOrderService.getById(id);
        Assert.notNull(inOrder, "入库单信息异常，请联系管理员！");
        Assert.isTrue(inOrder.getStatus().equals(WarehouseConstant.EnterWarehouseEnum.RECEIVE_END.getCode()), "入库单不是进行中状态，无需强制结束！");

        // 更新入库单
        inOrder.setStatus(WarehouseConstant.EnterWarehouseEnum.FORCE_END.getCode());
        inOrder.setRemarks(inOrder.getRemarks() + "(强制结束原因：" + remarks + ")");
        inOrderService.updateById(inOrder);

        // 更新入库任务
        List<InOrderTaskMain> list = inOrderTaskMainService.list(new QueryWrapper<InOrderTaskMain>().eq("in_order_id", id));
        list.forEach(data -> data.setStatus(WarehouseConstant.EnterWarehouseEnum.FORCE_END.getCode()));
        inOrderTaskMainService.updateBatchById(list);

        // 更新入库任务详情
        inOrderTaskDetailService.update(
                new InOrderTaskDetail().setStatus(WarehouseConstant.EnterWarehouseDetailEnum.ENTER_END.getCode()),
                new QueryWrapper<InOrderTaskDetail>().in("in_order_task_id", list.stream().map(InOrderTaskMain::getId).collect(Collectors.toList()))
        );

        return Result.OK();
    }


    /**
     * 通过excel导入数据
     *
     * @param request
     * @param response
     * @return
     */
    @AutoLog(value = "入库管理-导入入库单")
    @ApiOperation(value = "入库管理-导入入库单", notes = "入库管理-导入入库单")
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
            MultipartFile file = entity.getValue();// 获取上传文件对象
            ImportParams params = new ImportParams();
            params.setTitleRows(2);
            params.setHeadRows(1);
            params.setNeedSave(true);
            try {
                List<InOrderPage> list = ExcelImportUtil.importExcel(file.getInputStream(), InOrderPage.class, params);
                for (InOrderPage page : list) {
                    InOrder po = new InOrder();
                    BeanUtils.copyProperties(page, po);
                    inOrderService.saveWarehousingEntry(po, page.getInOrderDetailList().stream().filter(data -> StringUtils.isNotBlank(data.getSkuCode())).collect(Collectors.toList()));
                }
                return Result.OK("文件导入成功！数据行数:" + list.size());
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return Result.error("文件导入失败:" + e.getMessage());
            } finally {
                try {
                    file.getInputStream().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return Result.OK("文件导入失败！");
    }

    /**
     * 导出excel
     *
     * @param request
     * @param inOrder
     */
    @AutoLog(value = "入库管理-导出入库单")
    @ApiOperation(value = "入库管理-导出入库单", notes = "入库管理-导出入库单")
    @RequestMapping(value = "/exportXls")
    public ModelAndView exportXls(HttpServletRequest request, InOrder inOrder) {
        // Step.1 组装查询条件查询数据
        QueryWrapper<InOrder> queryWrapper = QueryGenerator.initQueryWrapper(inOrder, request.getParameterMap());
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        //Step.2 获取导出数据
        List<InOrder> queryList = inOrderService.list(queryWrapper);
        // 过滤选中数据
        String selections = request.getParameter("selections");
        List<InOrder> inOrderList = new ArrayList<InOrder>();
        if (oConvertUtils.isEmpty(selections)) {
            inOrderList = queryList;
        } else {
            List<String> selectionList = Arrays.asList(selections.split(","));
            inOrderList = queryList.stream().filter(item -> selectionList.contains(item.getId())).collect(Collectors.toList());
        }
        // Step.3 组装pageList
        List<InOrderPage> pageList = new ArrayList<InOrderPage>();
        for (InOrder main : inOrderList) {
            InOrderPage vo = new InOrderPage();
            BeanUtils.copyProperties(main, vo);
            List<InOrderDetail> inOrderDetailList = inOrderDetailService.selectByMainId(main.getId());
            vo.setInOrderDetailList(inOrderDetailList);
            pageList.add(vo);
        }
        // Step.4 AutoPoi 导出Excel
        ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
        mv.addObject(NormalExcelConstants.FILE_NAME, "入库管理列表");
        mv.addObject(NormalExcelConstants.CLASS, InOrderPage.class);
        mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("入库管理数据", "导出人:" + sysUser.getRealname(), "入库管理"));
        mv.addObject(NormalExcelConstants.DATA_LIST, pageList);
        return mv;
    }

    @ApiOperation(value = "入库管理-打印标签品番编码下拉选项", notes = "入库管理-打印标签品番编码下拉选项")
    @PostMapping("/getInOrderShelfCodeOptions")
    public Result<?> getInOrderShelfCodeOptions(@RequestParam(name = "id", required = true) String id) {
        List<InOrderDetail> inOrderDetailList = inOrderDetailService.selectByMainId(id);
        Set<String> skuCodeSet = inOrderDetailList.stream().map(InOrderDetail::getSkuCode).collect(Collectors.toSet());
        return Result.OK(skuCodeSet);
    }

    /**
     * 打印标签
     *
     * @param
     * @throws PrintException
     * @throws IOException
     */
    @AutoLog(value = "入库管理-打印标签")
    @ApiOperation(value = "入库管理-打印标签", notes = "入库管理-打印标签")
    @PostMapping("/printLabel")
    public Result<?> printLabel(@RequestParam(name = "skuCode", required = true) String skuCode,
                                @RequestParam(name = "needQuantity", required = true) Integer needQuantity) {
        Assert.isTrue(needQuantity > 0, "打印标签的数量应大于0！");
        BasicSkuInfo basicSkuInfo = basicSkuInfoService.list(new LambdaQueryWrapper<BasicSkuInfo>().eq(BasicSkuInfo::getSkuCode, skuCode)).get(0);

        // 封装二维码的值
        String today = DateUtils.getToday();
        String skuType = Objects.equals(basicSkuInfo.getSkuType(), WarehouseConstant.SkuTypeEnum.PT.getCode()) ? "PT" : "SF";

        // 封装图片中的文字
        JSONObject rightText = new JSONObject();
        rightText.put("品番编码", basicSkuInfo.getSkuCode());
        rightText.put("生成日期", DateUtils.getNow("yyyy-MM-dd HH:mm:ss"));

        // 封装返回结果 多个base64Str
        List<String> base64StrList = new ArrayList<>();
        for (int i = 1; i <= needQuantity; i++) {
            String uniqueIdentification = redisUtil.incrementCounter("label", 10);
            String labelData = String.format("MA0791KQ54 %s %s %s%s", basicSkuInfo.getSkuCode(), today, skuType, uniqueIdentification);
            BufferedImage bufferedImage = QRCodeUtil.produceLabel(labelData, false, rightText);
            String base64Str = bufferedImageToBase64(bufferedImage);
            if (StringUtils.isNotBlank(base64Str)) {
                base64StrList.add(base64Str);
            }
        }
        return Result.OK(base64StrList);
    }

    public String bufferedImageToBase64(BufferedImage image) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "png", byteArrayOutputStream);
            byte[] imageBytes = byteArrayOutputStream.toByteArray();
            return Base64.getEncoder().encodeToString(imageBytes);
        } catch (Exception exception) {
            log.info("时间：{},bufferedImageToBase64方法出现异常：", LocalDateTime.now(), exception);
        }
        return null;
    }

    /**
     * 获取请求者ip地址
     *
     * @param request
     * @return
     */
    @PostMapping("/getIpAddress")
    public Result<?> getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多级代理，取第一个IP为客户端真实IP
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0];
        }
        return Result.OK(ip);
    }

}
